package com.demo.cryptology.asymmetric;

import sun.security.ec.ECKeyFactory;
import sun.security.ec.ECPrivateKeyImpl;
import sun.security.ec.ECPublicKeyImpl;
import sun.security.pkcs.PKCS8Key;
import sun.security.x509.X509Key;

import javax.crypto.Cipher;
import javax.crypto.NullCipher;
import java.math.BigInteger;
import java.security.Key;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * ECC椭圆曲线加密算法是一种公钥加密体制，最初由Koblitz和Miller两人于1985年提出，其数学基础是利用椭圆曲线上的有理点构成Abel加法群上椭圆离散对数的计算困难性。
 * 与经典的RSA，DSA等公钥密码体制相比，椭圆密码体制有以下优点：
 *  1.安全性高:有研究表示160位的椭圆密钥与1024位的RSA密钥安全性相同。
 *  2.处理速度快:在私钥的加密解密速度上，ecc算法比RSA、DSA速度更快。
 *  3.存储空间占用小。
 *  4.带宽要求低。
 * 原理
 * ECC是建立在基于椭圆曲线的离散对数问题上的密码体制，给定椭圆曲线上的一个点G，并选取一个整数k，求解K=kG很容易(注意根据kG求解出来的K也是椭圆曲线上的一个点)；反过来，在椭圆曲线上给定两个点K和G，若使K=kG，求整数k是一个难题。ECC就是建立在此数学难题之上,这一数学难题称为椭圆曲线离散对数问题。其中椭圆曲线上的点K则为公钥(注意公钥K不是一个整数而是一个椭圆曲线点，这个点在OpenSSL里面是用结构体EC_Point来表示的，为了加深理解，建议自行下载OpenSSL进行查看学习)，整数k则为私钥(实际上是一个大整数)。
 */
public class ECC {
    public static final String ECC = "ECC";
    public static final String PUBLIC_KEY = "ECCPublicKey";
    public static final String PRIVATE_KEY = "ECCPrivateKey";


    public static Map<String, Object> initKey() throws Exception {
        BigInteger px = new BigInteger("2fe13c0537bbc11acaa07d793de4e6d5e5c94eee8", 16);
        BigInteger py = new BigInteger("289070fb05d38ff58321f2e800536d538ccdaa3d9", 16);
        ECPoint g = new ECPoint(px, py);
        BigInteger n = new BigInteger("5846006549323611672814741753598448348329118574063", 10);
        int h = 2;
        int m = 163;
        int[] ks = {7, 6, 3};
        ECFieldF2m ecField = new ECFieldF2m(m, ks);
        BigInteger a = new BigInteger("1", 2);
        BigInteger b = new BigInteger("1", 2);
        EllipticCurve ellipticCurve = new EllipticCurve(ecField, a, b);
        ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, g, n, h);
        ECPublicKey publicKey = new ECPublicKeyImpl(g, ecParameterSpec);
        BigInteger s = new BigInteger("1234006549323611672814741753598448348329118574063", 10);
        ECPrivateKey privateKey = new ECPrivateKeyImpl(s, ecParameterSpec);
        Map<String, Object> keyMap = new HashMap<>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    public static byte[] encrypt(byte[] data, String privateKey) throws Exception {
        byte[] keyByte = decryptBASE64(privateKey);
        X509Key x509Key = new X509Key();
        x509Key.decode(keyByte);
        ECPublicKey pubKey = (ECPublicKey) ECKeyFactory.toECKey(x509Key);
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(pubKey.getW(), pubKey.getParams());
        Cipher cipher = new NullCipher();
        cipher.init(Cipher.ENCRYPT_MODE, pubKey, ecPublicKeySpec.getParams());
        return cipher.doFinal(data);
    }


    public static byte[] decrypt(byte[] data, String key) throws Exception {
        byte[] keyByte = decryptBASE64(key);
        PKCS8Key pkcs8Key = new PKCS8Key();
        pkcs8Key.decode(keyByte);
        ECPrivateKey priKey = (ECPrivateKey) ECKeyFactory.toECKey(pkcs8Key);
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(priKey.getS(), priKey.getParams());
        //目前不支持EC算法
        Cipher cipher = new NullCipher();
        cipher.init(cipher.DECRYPT_MODE, priKey, ecPrivateKeySpec.getParams());
        return cipher.doFinal(data);
    }


    public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return encryptBASE64(key.getEncoded());
    }

    public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return encryptBASE64(key.getEncoded());
    }


    public static byte[] decryptBASE64(String data) {
        return Base64.getDecoder().decode(data);
    }

    public static String encryptBASE64(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }


    public static void main(String[] args) throws Exception {
        String dataStr = "fasdfasdfas";
        byte[] data = dataStr.getBytes();
        Map<String, Object> keyMap = initKey();
        String publicKey = getPublicKey(keyMap);
        String privateKey = getPrivateKey(keyMap);
        System.out.println("公钥:\n" + publicKey);
        System.out.println("私钥:\n" + privateKey);

        System.out.println("原数据:" + dataStr);
        byte[] encodeData = encrypt(data, publicKey);
        System.out.println("加密后:" + new String(encodeData));
        byte[] decodeData = decrypt(encodeData, privateKey);
        System.out.println("解密后:" + new String(decodeData));
    }
}
